;   This program is free software: you can redistribute it and/or modify
;   it under the terms of the GNU General Public License as published by
;   the Free Software Foundation, either version 3 of the License, or
;   (at your option) any later version.
;
;   This program is distributed in the hope that it will be useful,
;   but WITHOUT ANY WARRANTY; without even the implied warranty of
;   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;   GNU General Public License for more details.
;
;   You should have received a copy of the GNU General Public License
;   along with this program.  If not, see <http://www.gnu.org/licenses/>.
;
;程序名称:StudREC.asm
;功能:學生積分记录表
;环境:16BIT DOS实模式(windows/dos或dosbox)
;编译器:MASM 5.1-6X 
;用法:看说明
;返回值:没有
;破坏寄存器:不适用
;
;这是贴吧上一个问题
;http://tieba.baidu.com/p/3861969926
;成绩管理模块，完成学生的姓名和成绩输入，计算出平均成绩，
;并显示出来。其中，把学生的成绩显示出来，及格的用蓝色，
;不及格用红色显示。输入界面参考如下：“时候需要输入”
;“请输入学生姓名”“请输入学生成绩”
;
;这是根据NameTelREC.asm的代码，因应要求做了小小修改，加了平均分和上色
;
;注意:这只是一个范例程式，具备最起码的功能，没有太花巧的技术。
;若你需要其他功能或加入或删减任何代码，请随便，但不能要求作者为你订制特定的功能或
;根据功课要求而改变这个子程序改变那个子程序，若是代码错误或优化代码的意见，
;则欢迎提出:)
;
;没多少空闲，程序只加了一些简单解释，若要探入理解代码运作，
;请自行debug，不要问我，或要求增加注解。
;
;--------------------------------------------------------------------------------
        max_name equ 20  ;姓名最大资料位数
	max_Score_Len equ 5  ; 3 Ascii (100)+ 0 +  db (255) 分数最大位数
	max_StudNo equ 2 ;学生编号最大位数
	max_record equ 9 ;最大记录个数
	max_rows equ 5 ;最大列
	max_Score equ 100 ;最大分数
	pass_score equ 60 ;合格分数
	Menu_x equ 22  ;以下是一般常数,望文生义
	Menu_y equ 1
	Bar_L equ 19
	up_key equ 48h
	down_key equ 50h
	enter_key equ 0dh
	ESC_key equ 1bh
	pass_color equ 1
	fail_color equ 4
	dot_limit equ 2  ;小数点后个数 

Data segment 
	inputName_str db 0dh,0ah,09,09,'Input NAME:$'    ;以下定义字串
	inputScore_str db 0dh,0ah,09,09,'Input Score:$'
	inputStudNo_str db 0dh,0ah,09,09,09,'INPUT Student No.:$'
	inputDel_str db 0dh,0ah,09,09,09,'Input Delete Student No.:$'
	no_num_str db 0dh,0ah,09,09,09,'NO THIS NUMBER'
	pause_str db 0dh,0ah,09,09,09,'Hit a key to continue....$'
	y_n_str db 0dh,0ah,09,09,09,'Delete this record ? (y/n)$'
	delete_add dw 0
	full_str db 0dh,0ah,09,09,09,'Data Full$'
Menu_str label byte   ;以下定义menu 字串
	db ' - Student Score Menu -',0dh,0ah,09,09,09
	db '1. Input Record',0dh,0ah,09,09,09
	db '2. Query Record',0dh,0ah,09,09,09
	db '3. Delete Record',0dh,0ah,09,09,09
	db '4. List Record',0dh,0ah,09,09,09
	db '5. Exit',0dh,0ah,09,09,09
	db 18h,19h,' to Choose & <Enter>$'
sub_table label byte  ;以下定义各子程序进入点
	dw offset INRECORD
	dw offset PRINT
	dw offset Del_REC
	dw offset LIST_REC
	dw offset QUIT
	input_buffer db 20,0,20 dup (0)  ;输入缓冲
	Xrecord_L equ (max_StudNo + max_name + max_Score_Len)  ;一项记录的大小
	Xrecord db Xrecord_L * max_record dup (0),0,0 ;end of record  ;总记录大小
disp_title label byte 
	db 0dh,0ah,09,09, Xrecord_L + 10 dup  ('-')  ;显示记录的边框
	db 0dh,0ah,09,09,'No.',20h,'Name',max_name - 4 dup (20h),20h,20h,'Score.',0dh,0ah,'$' ;;显示记录标题
	disp_record_str db 09,09  ;显示的每一项记录的缓冲
	disp_record db (Xrecord_L + 10) dup (20h),'$'  
	menu_pointer dw 0  ;表单指标,可由上下键改变
	pass_str db ' (Pass)'  ;合格
	pass_L equ $ - offset pass_str
	fail_str db ' (Fail)'  ;不合格
	Average_str db 0dh,0ah,09,09, 'Average Score:','$' 
	student_count dw 0  ;学生总数,视输入/删除而变动
	sum dw 0  ;所有学生总分
Data Ends

STACK SEGMENT STACK
	DB 100H DUP(?)
STACK ENDS

code segment
assume ds:data,cs:code,SS:STACK
begin:
	mov ax,data
	mov ds,ax
	mov es,ax
	cld
displayMenu:
	call cls  ;清屏
	mov dl,Menu_x + 2   ;表单位置
	mov dh,Menu_y - 1
	call SetCur ;设定光标
	mov dx,offset Menu_str ;print menu
	mov ah,9
	int 21h
	mov menu_pointer,0   ; 表单移动指标初始化
	mov cx,3030h
	call SetCurSz ;close cursor  ;关闭光标
redraw:
	call draw_arrow  ; 根据表单指标(0-4)绘画箭号 > < ,删除旧箭号
rekey:
	mov ax,0c07h ;Clear Buffer and Invoke Keyboard Function
	int 21h  ;读键
	or al,al  ;是否0 ,0则可能是扩充键,即方向等键...
	jz rekey5  ;returns 0 for extended keystroke
	cmp al,Esc_key
	jnz rekey2
	call quit  ;离开
rekey2:
	cmp al,enter_key  ;回车
	jz choose_Ok
	mov ah,al   ;以下判别是否1-5的数字,若是则进入相关的子程序 
	sub ah,'0'
	cmp ah,1
	jb rekey
	cmp ah,max_rows
	ja rekey
	mov bl,ah
	dec bl
	mov bh,0
	mov menu_pointer,bx   ;取得新的表单指标(0-4)
	call draw_arrow ;根据表单指标(0-4)绘画箭号 > < ,删除旧箭号
	jmp short choose_ok
rekey5:
	mov ah,7 ;read extended keystroke ;读扩充键
	int 21h   
	cmp al,up_key  ;以下判别是否上下
	jnz rekey10
	cmp menu_pointer,0
	jz rekey
	dec menu_pointer
	jmp short redraw
rekey10:
	cmp al,down_key
	jnz rekey
	cmp menu_pointer,max_rows - 1
	jae rekey
	inc menu_pointer
	jmp short redraw
choose_Ok:
	mov cx,0c0dh    ;开启光标,准备使用者输入记录
	call SetCurSz ;open cursor
	mov dh,menu_y + max_rows
	mov dl,0
	call SetCur 
	mov bx,menu_pointer
	shl bx,1
	add bx,offset sub_table ;point to sub-menu offset
	call word ptr [bx]   ;根据表单指标进入对应的子程序
	jmp displayMenu ;循环
;-------------
INRECORD:   ;输入姓名/分数子程序 (程式以记录编号是否0来判别该记录位置是否已占用,
	mov si,offset Xrecord  ;找到第一个0的记录才开始接受输入,若记录已满则离开)
	mov cx,1 ;StudNo no.   ;first record ;
inR_10:
	mov ax,[si]  ;get record no. 
	cmp ax,0  ;is it empty ?  ;是否0
	jz inR_20 ;yes  是
	add si,Xrecord_L ;try next record buffer  ,指向下一记录
	inc cx ; next  
	cmp cx,max_record ; is it full ? 是否已满
	jbe inR_10 ; no   未
	call print_full   ;若满印出record  full 
	call pause_proc  ;print pause
	ret ;leave
inR_20:
	mov [si],cx  ;save record no. 存入记录编号
inR_30:
	call INNAME  ;get name input ;输入姓名
	mov cl,input_buffer + 1 ;get input length
	mov ch,0  ; clear
	jcxz inR_30 ;no input
	mov di,si ;save name
	add di,2 ;by pass record no.
	mov si,offset input_buffer + 2  ;source 
	push di ;--------save ;保存
	rep movsb ;move name to my record  ;存姓名
	mov al,0  ;make zero
	stosb
inR_40:
	call Score ;get Score no.  ;输入分数,AX传回分数值
	pop di ;---------restore ;取回
	add di,max_name 
	push di	
	mov si,offset input_buffer + 2
	rep movsb ;save score to my record ;存文字ascii分数
	mov ah,0 
	mov [di],ah 
	pop di 
	add di,max_Score_Len - 1 ;bypass ascii 3 + 1
	stosb	;store score ;存分数值
	ret
;-------------
print_full:  
	mov dx,offset full_str
	mov ah,9
	int 21h
	ret
;-------------
INNAME:  ;输入姓名子程序
	mov ah,9
	mov dx,offset inputName_str
	int 21h
	mov input_buffer,max_name - 1
	mov input_buffer + 1,0
	mov ah,10  ;get input
	mov dx,offset input_buffer
	int 21h
	ret
;-------------
Score: ;输入分数子程序
	mov ah,9
	mov dx,offset inputScore_str
	int 21h
Score10:
	mov input_buffer,max_Score_Len - 1 ;4,0,4 
	mov input_buffer + 1,0
	mov ah,10 ;get input
	mov dx,offset input_buffer
	int 21h
	;count

	mov cl,input_buffer + 1
	mov ch,0
	jcxz Score10  ;输入0,再输入
	mov bp,cx
	mov bx,0
	lea si,input_buffer + 2
	mov di,10
Score15:  ;以下是读入分数输入,再转换成数值,方法是每读入一字节,前一字节x10,加上新字节,如此循环则可
	lodsb
	cmp al,0dh  ;回车
	jz Score30
	cmp al,'0' ;以下判别0-9
	jb Score
	cmp al,'9'
	ja Score
	sub al,'0'
	mov ah,0
	xchg bx,ax  ;交换
	mul di  ;乘10 
	add ax,bx ;累加
	mov bx,ax ;保存 
	dec bp  ;下一个
	jnz Score15
	jmp short Score30

Score30:	mov ah,0
	cmp ax,Max_score ;100  ;取得值在AL,是否100
	ja Score ;大于,不接受,则再输入

	ret  ;正常返回,AX传回分数值

;-------------
Del_REC:   ;删除记录子程序
	mov ah,9
	mov dx,offset inputDel_str
	int 21h
	call Get_key   ;询问删除哪个记录
	jnc DelR10 
	ret
DelR10:
	mov ah,0  ;clear 
	call get_record  ;search record  ;根据使用者输入编号,找记录
	jnc DelR20  ;找到
	jmp print20 ; no such record   ;找不到
DelR20:
	call print_record  ;print record ;印出该记录
	mov dx,offset y_n_str
	mov ah,9  
	int 21h
DelR30:
	mov ah,7  ;wait a key  ;询问是否真的删除 (Y/N)
	int 21h
	cmp al,1bh ;ESC
	jz DelR35
	and al,11011111b ;upcase
	cmp al,'Y'
	jnz DelR40
	int 29h
	mov si,delete_add  ;取得删除记录的第一字节,即编号
	mov word ptr [si],0 ;clear record ;置0,表示已删,下次输入新记录时可用
DelR35:
	ret
DelR40:
	cmp al,'N'
	jnz DelR30
	int 29h
ret
;-------------
PRINT:  ;query  ;查询记录子程序
	call clear_display
	mov ah,9
	mov dx,offset inputStudNo_str
	int 21h
	mov cx,2
	call Get_key  ;查哪一个
	jnc print10
	ret
print10:
	mov ah,0  ;clear 
	call get_record  ;search record ;找到,取得记录,并由ax传回分数
	jnc print60  ;ok  ;找到
print20:
	mov dx,offset no_num_str ;fail  ;找不到
	jmp short print70
print60:
	push ax	   ;ax = score 保存分数
	call print_record  ;print record
	pop ax  ;取回分数
	call print_PassFail  ;根据分数印出合格/不合格

pause_proc:   
	mov dx,offset pause_str
print70: 
	mov ah,9
	int 21h
	mov ah,7  ;wait a key
	int 21h
	ret
;-------------
LIST_REC:  ;列出所有纪录子程序
	call clear_display  ;清除显示记录缓冲区
	mov dx,offset disp_title
	mov ah,9
	int 21h
	mov bp,0  ; first record
	xor ax,ax
	mov sum,ax  ;clear  总分初始化
	mov student_count,ax ;clear ;学生总数初始化

LIST10:
	mov ax,bp   ;哪个记录
	call get_record  ;next record ;取得
	jc LIST15  ;该记录是否已占用,即有没有? CF=1即没有,不用印
	push ax ;保存
	add sum,ax  ;分数加总
	inc  student_count  ;记录累加
	call print_RecordLine ;印出该记录
	pop ax ; score in AX  ;回存
	call print_PassFail  ;根据分数印出合格/不合格
	call print_enter  ;回车

LIST15:
	inc bp  ;next ;下一记录
	cmp bp,max_record  ;is it finish ? ;是否到限制值
	jbe list10 ;no ;未,回去再印
	cmp student_count,0  ;记录累加是否0,若0则不用计平均,走人
	jz LIST20
	mov dx,offset Average_str
	mov ah,9
	int 21h
	mov dx,0
	mov ax,sum  ;取总分
	div student_count  ;1-9  ;除记录累加,即学生总数
	; ax.dx  ax是商,dx是余数
	call print_average  ;印出平均,若小数亦印出

LIST20:
	call pause_proc ;with a key
listx:
	ret
;-------------
QUIT:
	call cls ;清屏
	mov ah,4ch  ;quit to dos
	int 21h
;-------------
cls: ;清屏子程序
	mov ah,0fh                  ;get display mode to al
	int 10h
	mov ah,0                    ;Set display mode
	int 10h
	ret
;---------------------------------------------------------
get_record:  ;record no. in ax 0-x  ;根据AX的记录编号读取该记录的子程序
	mov bx,Xrecord_L   ;record length 
	mul bx
	mov si,offset Xrecord  
	add si,ax  ;pointer to  record 指向该记录
	cmp word ptr [si],0  ;occupancy ?  0则可用
	jnz get_10    ;no 
	stc
	ret
get_10:	call clear_display  ;清除显示记录缓冲区
	mov delete_add,si  ;记下位置
	mov di,offset disp_record
	lodsw	;get no.   以下根据编号值印出ASCII文字
	add al,0   ;BCD ajust
	aam  ; change ot BCD format
	or ax,3030h  ;change to ASCII
	xchg al,ah  
	stosw  ;store it
	mov ax,2020h  ;space
	stosw 
	push si
	push di
get_20:
	lodsb  ;load name record to  display buffer ;取得姓名
	or al,al
	jz get_30
	stosb
	jmp short get_20
get_30: 
	pop di
	pop si
	add si,max_name  ;next
	add di,max_name + 2

	mov bx,si

get_45:	lodsb  ;load Score. record to  display buffer;最得分数ascii
	or al,al
	jz get_47
	stosb
	jmp short get_45

get_47:	
	mov si,bx
	add si,max_Score_Len - 1 ; point to score value 
	lodsb  ;取回分数值,传回
	;return Ax = score

get_x:
	clc
	ret
;-------------------------------------------------
print_record: ;印记录子程序
	mov dx,offset disp_title
	mov ah,9
	int 21h
print_RecordLine:
	mov dx,offset disp_record_str
	mov ah,9
	int 21h
	ret
;-------------------------------------------------
Get_key:  ;读输入子程序
	mov ah,7 ;read key
	int 21h
	cmp al,0dh ;is it enter?
	jnz GetK10 ;no
GetK5:
	stc
	ret ;quit 
GetK10:
	cmp al,1bh ;is it ESC ?
	jz GetK5 ;no
	cmp al,'1'  ;check 0-9
	jb Get_key  ;not in range
	cmp al,'9'
	ja Get_key  ;not in range
	int 29h ;print al 
	sub al,'0'  ;ASCII change to value
	dec al    ;1-9 -> 0-8 record begin with 0
	clc
	ret
;-----------------------------------
;Menu_x equ 26  
;Menu_y equ 2
;Bar_L equ 17
draw_arrow:     ;根据表单指标印出> < 子程序
	mov cx,5
	mov dl,Menu_x
	mov dh,Menu_y
draw10:
	call SetCur
	mov al,20h ; ;clear arrow
	int 29h
	inc dh
	loop draw10
;
	mov cx,5
	mov dl,Menu_x +  Bar_L
	mov dh,Menu_y
draw20:
	call SetCur
	mov al,20h ;clear arrow
	int 29h
	inc dh
	loop draw20
;
	mov ax,menu_pointer
	mov dh,Menu_y
	add dh,al
	mov dl,Menu_x
	call SetCur
	mov al,'>'
	int 29h
;
	mov dl,Menu_x +  Bar_L
	call SetCur
	mov al,'<'
	int 29h
	ret
;-----------------------------------
SetCur:  ;设定光标位置
	mov bh,0
	mov ah,2
	int 10h
	ret
;-----------------------------------
SetCurSz:   ;设定光标大小    
	mov ah,01
	int 10h
	ret
;-----------------------------------
backspace: ;backspace模拟
	mov dl,8
	mov ah,2
	int 21h
	mov dl,20h
	int 21h
	mov dl,8
	int 21h
	ret
;-------------------------
print_color:  ;si = offset , bp = count ;利用int10h/ah=09h 印出指位置si,指定个数bp,bl=颜色的字节
	mov ah,3
	mov bh,0
	int 10h ;get cursor position
re_print:
	push dx ;save it
	lodsb
	mov ah,9
	mov bh,0
	mov cx,1
	;mov bl,red_color
	int 10h  ;print 1 bytes with red color
	pop dx  ;restore cursor position 
	inc dl  ; forward 1 byte
	mov bh,0
	mov ah,2 ;get new cursor position
	int 10h     
	dec bp  ;next bytes
	jnz re_print 
	ret
;---------------------------- 
print_enter: ;回车
	mov dl,0dh
	mov ah,2
	int 21h
	mov dl,0Ah  ;print enter, next line
	int 21h
	ret
;---------------------------- 
print_PassFail: ;印出合格/不合格子程序
    lea si,pass_str  ;合格字串
	mov bl,pass_color ;合格颜色
	cmp al,pass_score 	;比较合格数,大于
	jae Pf10  则跳
	mov bl,fail_color  ;这里是小于,不合格颜色
	add si,Pass_L	;fail str ;不合格字串
Pf10:	push bp ;保存,因主程序有用到bp,必须保存
	mov bp,Pass_L ;长度
	call print_color ;呼叫印出合格/不合格 的颜色字串
	pop bp 取回
	ret		
;---------------------------- 
print_average:  ;ax . dx  ;印出平均,包含小数
	xor cx,cx
	add al,0
	aam
	push ax	;save al
	inc cx
	or ah,ah     ;ah = zero, 0-9 only
	jz print_a20
	cmp ah,0Ah
	jb print_a10
	mov al,ah
	add al,0
	aam
	push ax
	inc cx

print_a10:	mov al,ah
	push ax
	inc cx

print_a20:	pop ax
	or al,'0'
	int 29h
	loop print_a20

	cmp dx,0
	jz print_a90

	mov al,'.'
	int 29h
	mov ax,dx
	mov bx,10
 	mov cx,dot_limit ;2 位小数
	xor dx,dx
print_a30:
	mul bx
	xor dx,dx
	div student_count
	or al,30h
	int 29h
	mov ax,dx
	or dx,dx
	jz print_a90
	loop print_a30

print_a90:
	ret		
;---------------------------- 
clear_display: ;清除显示记录缓冲区
	mov di,offset disp_record
	mov al,20h
	mov cx,(Xrecord_L + 10) - 1
	rep stosb ; clear display buffer
	ret
;---------------------------- 
	code ends
	end begin